AI解析を使った画像シェアWEBアプリを超簡単に作る【DI編】#reinvent
*このエントリはシリーズ5番目の記事です。
Conect React Component を使って動的にGraphQL APIを呼び出し返って来たデータを表示する処理を書きます。 具体的にはアルバムのリストを取得し、画面に表示させます。
react-router-domモジュールを追加
npm install --save react-router-dom
App.jsにコードを追加してゆきましょう。
モジュール&コンポーネントの追加
必要なモジュールをApp.jsに追加します。
- semantic-ui-reactを追加
- react-router-domからroutingを追加
- aws-amplifyからAPI & graphqlOperationを追加
- aws-amplify-reactからConnectコンポーネントを追加
import React, { Component } from 'react'; import { Grid, Header, Input, List, Segment } from 'semantic-ui-react'; import {BrowserRouter as Router, Route, NavLink} from 'react-router-dom'; import Amplify, { API, graphqlOperation } from 'aws-amplify'; import { Connect, withAuthenticator } from 'aws-amplify-react'; import aws_exports from './aws-exports'; Amplify.configure(aws_exports);
データをアルファベット順にソート
取得したデータをソートする関数を追加します。
function makeComparator(key, order='asc') { return (a, b) => { if(!a.hasOwnProperty(key) || !b.hasOwnProperty(key)) return 0; const aVal = (typeof a[key] === 'string') ? a[key].toUpperCase() : a[key]; const bVal = (typeof b[key] === 'string') ? b[key].toUpperCase() : b[key]; let comparison = 0; if (aVal > bVal) comparison = 1; if (aVal < bVal) comparison = -1; return order === 'desc' ? (comparison * -1) : comparison }; }
クエリを変数に追加
ListAlbums、 SubscribeToNewAlbums、 GetAlbum、それぞれのクエリ変数を作成します。
const ListAlbums = `query ListAlbums { listAlbums(limit: 9999) { items { id name } } }`; const SubscribeToNewAlbums = ` subscription OnCreateAlbum { onCreateAlbum { id name } } `; const GetAlbum = `query GetAlbum($id: ID!) { getAlbum(id: $id) { id name } } `;
新規アルバム作成と表示
class NewAlbum extends Component { constructor(props) { super(props); this.state = { albumName: '' }; } handleChange = (event) => { let change = {}; change[event.target.name] = event.target.value; this.setState(change); } handleSubmit = async (event) => { event.preventDefault(); const NewAlbum = `mutation NewAlbum($name: String!) { createAlbum(input: {name: $name}) { id name } }`; const result = await API.graphql(graphqlOperation(NewAlbum, { name: this.state.albumName })); console.info(`Created album with id ${result.data.createAlbum.id}`); this.setState({ albumName: '' }) } render() { return ( <Segment> <Header as='h3'>Add a new album</Header> <Input type='text' placeholder='New Album Name' icon='plus' iconPosition='left' action={{ content: 'Create', onClick: this.handleSubmit }} name='albumName' value={this.state.albumName} onChange={this.handleChange} /> </Segment> ) } }
アルバムリストの取得と表示
class AlbumsList extends React.Component { albumItems() { return this.props.albums.sort(makeComparator('name')).map(album => <List.Item key={album.id}> <NavLink to={`/albums/${album.id}`}>{album.name}</NavLink> </List.Item> ); } render() { return ( <Segment> <Header as='h3'>My Albums</Header> <List divided relaxed> {this.albumItems()} </List> </Segment> ); } } class AlbumsListLoader extends React.Component { onNewAlbum = (prevQuery, newData) => { // アルバムが新規に追加された時、アルバムリストを更新 let updatedQuery = Object.assign({}, prevQuery); updatedQuery.listAlbums.items = prevQuery.listAlbums.items.concat([newData.onCreateAlbum]); return updatedQuery; } render() { return ( <Connect query={graphqlOperation(ListAlbums)} subscription={graphqlOperation(SubscribeToNewAlbums)} onSubscriptionMsg={this.onNewAlbum} > {({ data, loading }) => { if (loading) { return <div>Loading...</div>; } if (!data.listAlbums) return; return <AlbumsList albums={data.listAlbums.items} />; }} </Connect> ); } }
アルバムの詳細を取得
class AlbumDetailsLoader extends React.Component { render() { return ( <Connect query={graphqlOperation(GetAlbum, { id: this.props.id })}> {({ data, loading }) => { if (loading) { return <div>Loading...</div>; } if (!data.getAlbum) return; return <AlbumDetails album={data.getAlbum} />; }} </Connect> ); } } class AlbumDetails extends Component { render() { return ( <Segment> <Header as='h3'>{this.props.album.name}</Header> <p>TODO: Allow photo uploads</p> <p>TODO: Show photos for this album</p> </Segment> ) } }
ホーム画面へコンポーネントを表示
class App extends Component { render() { return ( <Router> <Grid padded> <Grid.Column> <Route path="/" exact component={NewAlbum}/> <Route path="/" exact component={AlbumsListLoader}/> <Route path="/albums/:albumId" render={ () => <div><NavLink to='/'>Back to Albums list</NavLink></div> } /> <Route path="/albums/:albumId" render={ props => <AlbumDetailsLoader id={props.match.params.albumId}/> } /> </Grid.Column> </Grid> </Router> ); } }
全部加えると以下のようなコードになります。
import React, { Component } from 'react'; import { Grid, Header, Input, List, Segment } from 'semantic-ui-react'; import {BrowserRouter as Router, Route, NavLink} from 'react-router-dom'; import Amplify, { API, graphqlOperation } from 'aws-amplify'; import { Connect, withAuthenticator } from 'aws-amplify-react'; import aws_exports from './aws-exports'; Amplify.configure(aws_exports); function makeComparator(key, order='asc') { return (a, b) => { if(!a.hasOwnProperty(key) || !b.hasOwnProperty(key)) return 0; const aVal = (typeof a[key] === 'string') ? a[key].toUpperCase() : a[key]; const bVal = (typeof b[key] === 'string') ? b[key].toUpperCase() : b[key]; let comparison = 0; if (aVal > bVal) comparison = 1; if (aVal < bVal) comparison = -1; return order === 'desc' ? (comparison * -1) : comparison }; } const ListAlbums = `query ListAlbums { listAlbums(limit: 9999) { items { id name } } }`; const SubscribeToNewAlbums = ` subscription OnCreateAlbum { onCreateAlbum { id name } } `; const GetAlbum = `query GetAlbum($id: ID!) { getAlbum(id: $id) { id name } } `; class NewAlbum extends Component { constructor(props) { super(props); this.state = { albumName: '' }; } handleChange = (event) => { let change = {}; change[event.target.name] = event.target.value; this.setState(change); } handleSubmit = async (event) => { event.preventDefault(); const NewAlbum = `mutation NewAlbum($name: String!) { createAlbum(input: {name: $name}) { id name } }`; const result = await API.graphql(graphqlOperation(NewAlbum, { name: this.state.albumName })); console.info(`Created album with id ${result.data.createAlbum.id}`); this.setState({ albumName: '' }) } render() { return ( <Segment> <Header as='h3'>Add a new album</Header> <Input type='text' placeholder='New Album Name' icon='plus' iconPosition='left' action={{ content: 'Create', onClick: this.handleSubmit }} name='albumName' value={this.state.albumName} onChange={this.handleChange} /> </Segment> ) } } class AlbumsList extends React.Component { albumItems() { return this.props.albums.sort(makeComparator('name')).map(album => <List.Item key={album.id}> <NavLink to={`/albums/${album.id}`}>{album.name}</NavLink> </List.Item> ); } render() { return ( <Segment> <Header as='h3'>My Albums</Header> <List divided relaxed> {this.albumItems()} </List> </Segment> ); } } class AlbumDetailsLoader extends React.Component { render() { return ( <Connect query={graphqlOperation(GetAlbum, { id: this.props.id })}> {({ data, loading }) => { if (loading) { return <div>Loading...</div>; } if (!data.getAlbum) return; return <AlbumDetails album={data.getAlbum} />; }} </Connect> ); } } class AlbumDetails extends Component { render() { return ( <Segment> <Header as='h3'>{this.props.album.name}</Header> <p>TODO: Allow photo uploads</p> <p>TODO: Show photos for this album</p> </Segment> ) } } class AlbumsListLoader extends React.Component { onNewAlbum = (prevQuery, newData) => { // When we get data about a new album, we need to put in into an object // with the same shape as the original query results, but with the new data added as well let updatedQuery = Object.assign({}, prevQuery); updatedQuery.listAlbums.items = prevQuery.listAlbums.items.concat([newData.onCreateAlbum]); return updatedQuery; } render() { return ( <Connect query={graphqlOperation(ListAlbums)} subscription={graphqlOperation(SubscribeToNewAlbums)} onSubscriptionMsg={this.onNewAlbum} > {({ data, loading }) => { if (loading) { return <div>Loading...</div>; } if (!data.listAlbums) return; return <AlbumsList albums={data.listAlbums.items} />; }} </Connect> ); } } class App extends Component { render() { return ( <Router> <Grid padded> <Grid.Column> <Route path="/" exact component={NewAlbum}/> <Route path="/" exact component={AlbumsListLoader}/> <Route path="/albums/:albumId" render={ () => <div><NavLink to='/'>Back to Albums list</NavLink></div> } /> <Route path="/albums/:albumId" render={ props => <AlbumDetailsLoader id={props.match.params.albumId}/> } /> </Grid.Column> </Grid> </Router> ); } } export default withAuthenticator(App, {includeGreetings: true});
Demo
アルバムを追加してみましょう。
次章ではAlbumに画像ファイルをストアできるようS3ストレージを追加します。